﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using Newtonsoft.Json;
using PowerWaveAddin.BusinessObjects;
using PowerWaveAddin.Uploaders;
using VistaControls;
using Newtonsoft.Json.Converters;
using PowerWaveAddin.Json;

namespace PowerWaveAddin
{
	public partial class UploadForm : Form
	{
		#region Events/Delegates

		public delegate void UploadCompleteHandler(object sender, UploadCompleteEventArgs args);

		private event UploadCompleteHandler m_UploadComplete;
		public event UploadCompleteHandler UploadComplete
		{
			add { m_UploadComplete += (value); }
			remove { m_UploadComplete -= (value); }
		}

		#endregion

		#region Fields

		private UploadArgs m_UploadArgs;
		private bool m_UploadCompleteFired = false;

		#endregion

		private BackgroundWorker m_UploadWorker = new BackgroundWorker();
		private static VistaControls.Dwm.Margins margins = new VistaControls.Dwm.Margins(5);

		private static UploadForm instance;
		public static UploadForm Instance
		{
			get
			{
				if (instance == null)
				{
					instance = new UploadForm();
				}

				return instance;
			}
		}

		private UploadForm()
		{
			InitializeComponent();

			m_UploadWorker.DoWork += new DoWorkEventHandler(OnPerformUpload);
			m_UploadWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnUploadComplete);
            
            UpdateGlass();
		}

        void UploadForm_Resize(object sender, EventArgs e)
        {
            UpdateGlass();
        }

        private void UpdateGlass()
        {
            /*
            if (Aero.IsAeroSupported)
            {
                margins = new VistaControls.Dwm.Margins(
                    0,
                    0,
                    0,
                    Math.Max(0, this.ClientSize.Height - this.uploadConfigControl.Bottom));

                VistaControls.Dwm.GlassHelper.HandleBackgroundPainting(this, margins);
                VistaControls.Dwm.GlassHelper.HandleFormMovementOnGlass(this, margins);
                VistaControls.Dwm.DwmManager.EnableGlassFrame(this, margins);
            }
             */
        }

		/// <summary>
		/// Sets the upload parameters so that when the config is considered okay, an upload will begin
		/// </summary>
		/// <param name="DirectoryOfPictures_p"></param>
		/// <param name="GoogleWaveUser_p"></param>
		public void ShowAndUpload(string DirectoryOfPictures_p, string GoogleWaveUser_p)
		{
			m_UploadCompleteFired = false;
			m_UploadArgs = new UploadArgs()
			{
				DirectoryOfPictures = DirectoryOfPictures_p,
				GoogleWaveUser = GoogleWaveUser_p,
				Uploader = storageServiceComboBox.SelectedItem as AbstractUploader
			};
			this.Show();
		}

		private void UploadForm_Load(object sender, EventArgs e)
		{
			storageServiceComboBox.Items.Clear();
			IEnumerable<Type> uploaderTypes = Assembly.GetExecutingAssembly().GetTypes()
				.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(AbstractUploader)));
			foreach (Type uploaderType in uploaderTypes)
			{
				AbstractUploader uploader = Activator.CreateInstance(uploaderType) as AbstractUploader;
				uploader.UploadProgress += new AbstractUploader.ProgressEventHandler(OnUploadProgress);
				uploader.GuiControl.UploadConfigOk += new UploadConfigControl.UploadConfigOkHandler(OnUploadConfigOk);
				uploader.GuiControl.UploadConfigCancel += new UploadConfigControl.UploadConfigCancelHandler(OnUploadConfigCancel);
				storageServiceComboBox.Items.Add(uploader);
			}
		}

		private void UploadForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			e.Cancel = true;
			if (m_UploadWorker.IsBusy)
				return;
			else if (!m_UploadCompleteFired)
				RaiseUploadComplete(new UploadCompleteEventArgs() { PresentationKey = null, PresentationName = null });
			this.Hide();
		}

		private void storageServiceComboBox_SelectedIndexChanged(object sender, EventArgs e)
		{
			this.Controls.Remove(uploadConfigControl);
			m_UploadArgs.Uploader = storageServiceComboBox.SelectedItem as AbstractUploader;
			this.uploadConfigControl = m_UploadArgs.Uploader.GuiControl;
			AddAndAlignUploadControls();
		}

		private void AddAndAlignUploadControls()
		{
            //this.uploadConfigControl.Left = 0;
            this.uploadConfigControl.Left = this.storageServiceLabel.Left;
			this.uploadConfigControl.Top = this.storageServiceComboBox.Bottom + 15;
            this.Controls.Add(uploadConfigControl);
            
            this.uploadReporter.Left = this.uploadConfigControl.Left;
			this.uploadReporter.Top = this.uploadConfigControl.Bottom;
            this.uploadReporter.Height = 100;
			this.Width = this.uploadConfigControl.Right + 25;
			//this.Height = this.uploadReporter.Bottom + 80;
			this.uploadReporter.Visible = true;

            this.UpdateGlass();
		}

		/// <summary>
		/// Performs the upload of the slides and the presentation information to the app engine
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnPerformUpload(object sender, DoWorkEventArgs e)
		{
			// Export the slides as images
			UploadArgs args = e.Argument as UploadArgs;
			this.Invoke(this.uploadReporter.AddOperationToList, "Converting slides for upload");
			Globals.ThisAddIn.Application.ActiveWindow.Presentation.SaveCopyAs(
						args.DirectoryOfPictures,
						Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsPNG,
						Microsoft.Office.Core.MsoTriState.msoTriStateMixed);
			this.Invoke(this.uploadReporter.SetLastOperationStatus, "Done");

			AbstractUploader uploader = args.Uploader;
			try
			{
				// Get the list of files and sort it so that we have correct ordering
				this.Invoke(this.uploadReporter.AddOperationToList, "Verifying slide order");
				string[] files = Directory.GetFiles(args.DirectoryOfPictures, "*.png", SearchOption.TopDirectoryOnly);
				Array.Sort(files, new StringLogicalComparer());
				this.Invoke(this.uploadReporter.SetLastOperationStatus, "Done");

				// Upload the images
				this.Invoke(this.uploadReporter.AddOperationToList, "Uploading slides");
				uploader.Upload(args.DirectoryOfPictures, files);
				this.Invoke(this.uploadReporter.SetLastOperationStatus, "Done");
			}
			finally
			{
				// Cleanup the image files from the disk
				this.Invoke(this.uploadReporter.AddOperationToList, "Cleaning up temporary files");
				Directory.Delete(args.DirectoryOfPictures, true);
				this.Invoke(this.uploadReporter.SetLastOperationStatus, "Done");
			}

			Dictionary<string, Uri> fileUriMap = uploader.GetFileUriMapping();

			// Send the presentation json object to the app engine
			Presentation presentation = new Presentation()
			{
				author = args.GoogleWaveUser,
				currentSlideIndex = -1,
				date = DateTime.Now,
				title = args.DirectoryOfPictures
			};

			List<Slide> slideList = new List<Slide>();
			foreach (Uri slideUri in fileUriMap.Values)
			{
				slideList.Add(new Slide()
				{
					slideUrl = slideUri.ToString()
				});
			}
			presentation.slides = slideList;

			string jsonObject = JsonConvert.SerializeObject(presentation, new PowerPointJsonDateConverter());
			HttpWebRequest webRequest = HttpWebRequest.Create(UrlStrings.PresentationServiceUrl) as HttpWebRequest;
			webRequest.ContentType = "application/json";
			webRequest.ContentLength = jsonObject.Length;
			webRequest.Method = "POST";
			webRequest.GetRequestStream().Write(Encoding.UTF8.GetBytes(jsonObject), 0, jsonObject.Length);
			webRequest.GetRequestStream().Flush();
			using (HttpWebResponse response = webRequest.GetResponse() as HttpWebResponse)
			using (StreamReader reader = new StreamReader(response.GetResponseStream()))
			{
				if (response.StatusCode != HttpStatusCode.OK)
					throw new Exception("Ah snap! Your presentation json object was rejected!");
				e.Result = reader.ReadToEnd();
			}
		}

		/// <summary>
		/// Will initiate an upload if there are upload args present otherwise the window will close
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnUploadConfigOk(object sender, EventArgs e)
		{
			if (m_UploadArgs != null && !m_UploadWorker.IsBusy)
				m_UploadWorker.RunWorkerAsync(m_UploadArgs);
			else
				this.Close();
		}

		void OnUploadConfigCancel(object sender, EventArgs e)
		{
			this.Close();
		}

		/// <summary>
		/// This handles the upload progress from the selected uploader
		/// </summary>
		/// <param name="args"></param>
		private void OnUploadProgress(UploadProgressArgs args)
		{
			// TODO: Put progress window update stuff here
			double ratio = (double)args.LastSlideUploadedIndex / (double)args.TotalSlides;
			int percentage = (int)(ratio * 100);
			this.Invoke(this.uploadReporter.SetProgressPercentage, percentage);
		}

		/// <summary>
		/// Will throw any errors if any occured otherwise it will raise the completed event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnUploadComplete(object sender, RunWorkerCompletedEventArgs e)
		{
			this.Invoke(this.uploadReporter.SetProgressPercentage, 100);
			m_UploadCompleteFired = true;
			if (e.Error != null)
			{
				Aero.DisplayErrorMessage("Upload Error", e.Error.Message, e.Error.ToString());
				RaiseUploadComplete(new UploadCompleteEventArgs() 
				{ 
					PresentationKey = null,
					PresentationName = m_UploadArgs.DirectoryOfPictures
				});
			}
			else
			{
				RaiseUploadComplete(new UploadCompleteEventArgs() 
				{ 
					PresentationKey = e.Result.ToString(),
					PresentationName = m_UploadArgs.DirectoryOfPictures
				});
			}
			m_UploadArgs = null;
			this.Invoke(new MethodInvoker(this.Close));
		}

		private void RaiseUploadComplete(UploadCompleteEventArgs e)
		{
			if (m_UploadComplete != null)
				m_UploadComplete(this, e);
		}

		private class UploadArgs
		{
			public string DirectoryOfPictures { get; set; }
			public string GoogleWaveUser { get; set; }
			public AbstractUploader Uploader { get; set; }
		}
	}

	public class UploadCompleteEventArgs : EventArgs
	{
		public string PresentationKey { get; set; }
		public string PresentationName { get; set; }
	}
}
